import Timekeeper
import Util
import Vector
import Config
import Configurable
import math
import random
import G
import time

reachedradius = Config.reachedradius

aimdict = Config.aimdict

class Behavior(Configurable.Configurable, Timekeeper.Timekeeper):
   runtime_vars = ('ship',)
                   
   __slots__ = runtime_vars
   
   def __init__(self, ship=None, name="", description=""):
      Configurable.Configurable.__init__(self, locals())
      self.ship = ship
   
   def update(self, player_ship):
      pass

###############################################################################
###MOVE BEHAVIORS
###############################################################################

class Move_Behavior(Behavior):
   runtime_vars = ('ship_speed',)
   
   __slots__ = () + runtime_vars

   def __init__(self, **args):
      Behavior.__init__(self, **args)
      Configurable.Configurable.__init__(self, locals())
      self.ship_speed = self.ship.speed
      
   def extendvector(self, vector):
      if vector[0] != 0 or vector[1] != 0:
         return Vector.set_length(vector, self.ship_speed)
      else:
         return (0,0)
      
class do_nothing(Move_Behavior):
   def update(self, player_ship):
      return (0,0)

class const_velocity(Move_Behavior):
   __slots__ = ('velx',
                'vely')
   
   def __init__(self, velx=0, vely=0, **args):
      Move_Behavior.__init__(self, **args)
      Configurable.Configurable.__init__(self, locals())
   
   def update(self, player_ship):
      return (self.velx, self.vely)

class move_to(Move_Behavior):
   runtime_vars = ('cur_dest',
                   'dest_i',
                   'reached',
                   'pausetimer')

   __slots__ = ('dests',
                'patrol',
                'pause')
                
   def __init__(self, dests=[(0,0)], patrol=False, cur_dest=0, pause=0, **args):
      Move_Behavior.__init__(self, **args)
      Configurable.Configurable.__init__(self, locals())
      self.dest_i = 0
      self.cur_dest = self.dests[0]
      self.reached = False
      self.pausetimer = self.pause
   
   @classmethod
   def moveto(cls, ship, dest):
      destp = Vector.v_sub(dest, ship.rect.center)
      if Vector.length(destp) <= ship.stop_distance():
         return (0,0)
      else:
         return Vector.set_length(destp, ship.speed)
   
   def next_dest(self):
      self.dest_i += 1
      if self.dest_i >= len(self.dests):
         if self.patrol:
            self.dest_i = 0
         else:
            self.reached = True
            self.cur_dest = (0,0)
            return
      self.cur_dest = self.dests[self.dest_i]
   
   def update(self, player_ship):
      Move_Behavior.update(self, player_ship)
      if not self.reached:
         if self.pausetimer > 0:
            self.pausetimer -= self.frame()
         elif Vector.distance(self.cur_dest, self.ship.rect.center) <= self.ship.stop_distance():
            self.next_dest()
            self.pausetimer = self.pause
      else:
         return (0,0)
      return self.extendvector(Vector.v_sub(self.cur_dest, self.ship.rect.center))

class relative_move(move_to):
   __slots__ = ('vectors',)
   
   def __init__(self, vectors=[(0,0)], *args, **kwargs):
      move_to.__init__(self, *args, **kwargs)
      Configurable.Configurable.__init__(self, locals())
      cur_vector = self.vectors[self.dest_i]
      if len(self.ship.orders) > 0:
         shipdest = self.ship.orders[-1]
      else:
         shipdest = self.ship.rect.center
      self.cur_dest = Vector.v_add(shipdest, cur_vector)
   
   def next_dest(self):
      self.dest_i += 1
      if self.dest_i >= len(self.vectors):
         if self.patrol:
            self.dest_i = 0
         else:
            self.reached = True
            self.cur_dest = (0,0)
            return
      self.cur_dest = Vector.v_add(self.ship.rect.center, self.vectors[self.dest_i])

class space_invaders(relative_move):
   __slots__ = ('move_x',
                'move_y')

   def __init__(self, move_x, move_y, *args, **kwargs):
      relative_move.__init__(self, vectors=[(move_x, 0), (0, move_y),
                                            (-1 * move_x, 0), (0, move_y)],
                             patrol=True, *args, **kwargs)
      Configurable.Configurable.__init__(self, locals())
      
class rect(move_to):
   __slots__ = ('width',
                'height',
                'currentdest',
                'dir')
                
   def __init__(self, width=0, height=0, dir=1, currentdest=0, **args):
      Configurable.Configurable.__init__(self, locals())
      self.ship = args['ship']
      if self.dir >= 0 and self.dir != 1:
         self.dir = 1
      elif self.dir < 0 and self.dir != -1:
         self.dir = -1
      #Initialize patrol box
      if len(self.ship.orders) == 0:
         center = self.ship.rect.center
      else:
         center = self.ship.orders[-1]
      if self.currentdest == 1:
         startx = center[0] + self.width/2
         starty = center[1]
      elif self.currentdest == 2:
         startx = center[0]
         starty = center[1] + self.height/2
      elif self.currentdest == 3:
         startx = center[0]
         starty = center[1] - self.width/2
      else:
         startx = center[0]
         starty = center[1] - self.height/2
      if self.dir == 1:
         corners = ((startx - self.width/2, starty + self.height/2),
                      (startx - self.width/2, starty - self.height/2),
                      (startx + self.width/2, starty - self.height/2),
                      (startx + self.width/2, starty + self.height/2))
      else:
         corners = ((startx + self.width/2, starty + self.height/2),
                      (startx + self.width/2, starty - self.height/2),
                      (startx - self.width/2, starty - self.height/2),
                      (startx - self.width/2, starty + self.height/2))
      move_to.__init__(self, dests=corners, patrol=True, **args)

class random_points(Move_Behavior):
   __slots__ = ('boxwidth',
                'boxheight',
                'currentdest',
                'centerx',
                'centery',
                'pause',
                'timer')
   
   def __init__(self, boxwidth=Config.width, boxheight=Config.height/2,
                      centerx=Config.width/2, centery=None,
                      pause=0.0, **args):
      Move_Behavior.__init__(self, **args)
      Configurable.Configurable.__init__(self, locals())
      if centery is None:
         self.centery = self.boxheight / 2
      else:
         self.centery = centery
      self.timer = 0.0
      random.seed()
      self.currentdest = self.nextdest()
   
   def nextdest(self):
      return (self.centerx + random.randint(0, self.boxwidth) - self.boxwidth/2,
              self.centery + random.randint(0, self.boxheight) - self.boxheight/2)
   
   def update(self, player_ship):
      Move_Behavior.update(self, player_ship)
      if Vector.distance(self.ship.rect.center, self.currentdest) < reachedradius:
         self.currentdest = self.nextdest()
         self.timer += self.pause
      if self.timer > 0.0:
         self.timer -= self.frame()
         if self.timer < 0.0:
            self.timer = 0.0
         return (0,0)
      else:
         return move_to.moveto(self.ship, self.currentdest)

class dodge(Move_Behavior):
   runtime_vars = ('desty',
                   'destx')

   __slots__ = ('boxheight',) + runtime_vars
   
   def __init__(self, boxheight=Config.height/2, *args, **kwargs):
      Move_Behavior.__init__(self, *args, **kwargs)
      Configurable.Configurable.__init__(self, locals())
      self.desty = random.randint(0, self.boxheight)
      self.destx = random.randint(0, Config.width)
   
   def update(self, player_ship):
      Move_Behavior.update(self, player_ship)
      bigrect = self.ship.rect.inflate(20,20)
      shippos = self.ship.rect.center
      shipx = shippos[0]
      left_shots = 0
      right_shots = 0
      dodge = False
      for p in G.projectiles.sprites():
         if p.friendly:
            c = p.rect.center
            if Vector.rect_line_collide(c, Vector.v_add(c, p.velocity), bigrect):
               dodge = True
            if c[0] <= shipx:
               left_shots += 1
            else:
               right_shots += 1
      if abs(shippos[1] - self.desty) < reachedradius:
         self.desty = random.randint(0, self.boxheight)
      if abs(shippos[0] - self.destx) < reachedradius:
         cx = player_ship.rect.centerx
         if dodge:
            if (shipx + 100 < Config.width and left_shots >= right_shots) or shipx <= 0:
               #Go right
               self.destx = random.randint(shipx, shipx+100)
            else:
               #Go left
               self.destx = random.randint(max(0, shipx-100), shipx)
         else:
            if (shipx < Config.width and (cx + 100 < Config.width and cx <= shipx) or (cx < 100 and cx > shipx)) or shipx <= 0:
               #Go right
               self.destx = random.randint(min(cx+100,Config.width), Config.width)
            else:
               #Go left
               self.destx = random.randint(0, max(cx-100,0))
      return Vector.towards(shippos, (self.destx, self.desty), self.ship.speed, extend=True)
      
###############################################################################
###FIRE BEHAVIORS
###############################################################################

class Fire_Behavior(Behavior):
   runtime_vars = ('cur_burst_shots',
                   'weaponspeeds',
                   'weapons',
                   'weaponoffsets')

   __slots__ = ('aim',
                'burst_size',
                'weaponi') + runtime_vars
   
   def __init__(self, aim='aim_normal', burst_size=1, weaponi=None, **args):
      Behavior.__init__(self, **args)
      Configurable.Configurable.__init__(self, locals())
      self.aim = aimdict[aim]
      self.cur_burst_shots = 0
      if weaponi is None:
         self.weapons = self.ship.weapons
      else:
         self.weapons = [self.ship.weapons[weaponi]]
      self.weaponspeeds = [None] * len(self.weapons)
      self.weaponoffsets = [None] * len(self.weapons)
      for i, w in enumerate(self.weapons):
         if w is not None:
            self.weaponspeeds[i] = w.lattr('projectile_speed')
            self.weaponoffsets[i] = w.offset
      
   def done_firing(self):
      pass
   
   def fire(self, player_ship):
      self.fire_real(player_ship)
      if self.burst_size > 1:
         self.cur_burst_shots = self.burst_size - 1
      else:
         self.done_firing()
      
   def fire_real(self, player_ship):
      if self.aim == 0:
         if len(self.weapons) == 1:
            return self.ship.fire(weaponi=self.weaponi)
         else:
            return self.ship.fire()
      else:
         tl = self.ship.rect.topleft
         angles = list()
         if self.aim == 1:
            def dest(offset):
               return player_ship.rect.center
         elif self.aim == 2:
            def dest(offset):
               d = Vector.lead_target(offset,
                                      self.weaponspeeds[i],
                                      player_ship.rect.center,
                                      player_ship.velocity)
               if d is None:
                  return player_ship.rect.center
               else:
                  return d
         for i, w in enumerate(self.weapons):
            if w is not None:
               offset = Vector.v_add(self.weaponoffsets[i], tl)
               vector_to = Vector.v_sub(dest(offset), offset)
               angles.append(Vector.angle(vector_to))
            else:
               angles.append(None)
         if len(angles) == 1:
            return self.ship.fire(angle=angles[0], weaponi=self.weaponi)
         else:
            return self.ship.fire(angles=angles)
   
   def update(self, player_ship):
      if self.cur_burst_shots > 0:
         if self.fire_real(player_ship):
            self.cur_burst_shots -= 1
            if self.cur_burst_shots == 0:
               self.done_firing()

class fire(Fire_Behavior):
   def update(self, player_ship):
      self.fire_real(player_ship)

class fire_when_moving(Fire_Behavior):
   
   def __init__(self, **args):
      Configurable.Configurable.__init__(self, locals())
      Fire_Behavior.__init__(self, **args)
   
   def update(self, player_ship):
      Fire_Behavior.update(self, player_ship)
      if self.ship.velocity[0] != 0 or self.ship.velocity[1] != 0:
         self.fire(player_ship)

class fire_when_stopped(Fire_Behavior):
   
   def __init__(self, **args):
      Configurable.Configurable.__init__(self, locals())
      Fire_Behavior.__init__(self, **args)
   
   def update(self, player_ship):
      Fire_Behavior.update(self, player_ship)
      if self.ship.velocity[0] == 0 and self.ship.velocity[1] == 0:
         self.fire(player_ship)

class hold_fire(Fire_Behavior):
   def update(self, player_ship):
      pass

class periodic_fire(Fire_Behavior):
   runtime_vars = ('fire_delay',)

   __slots__ = ('fire_interval',
                'randomness') + runtime_vars
   
   def __init__(self, fire_interval, randomness=0, **args):
      Configurable.Configurable.__init__(self, locals())
      Fire_Behavior.__init__(self, **args)
      self.randomness = 2 * randomness
      self.fire_delay = self.fire_interval
   
   def done_firing(self):
      self.fire_delay -= (self.fire_interval + (random.random() - 0.5) * self.randomness)
   
   def update(self, player_ship):
      Fire_Behavior.update(self, player_ship)
      if self.fire_delay < self.fire_interval:
         self.fire_delay += self.frame()
      else:
         if self.cur_burst_shots == 0:
            self.fire(player_ship)

class timed_fire(Fire_Behavior):
   runtime_vars = ('shot_vectors',
                   'shot_speeds')
   
   __slots__ = () + runtime_vars
   
   def __init__(self, **args):
      Configurable.Configurable.__init__(self, locals())
      Fire_Behavior.__init__(self, **args)
      self.shot_vectors = list()
      self.shot_speeds = list()
      for w in self.weapons:
         if w is not None:
            for i, a in enumerate(w.lattr('def_muzzle_velocities')):
               self.shot_vectors.append(a)
               self.shot_speeds.append(Vector.length(a))

   def update(self, player_ship):
      Fire_Behavior.update(self, player_ship)
      shipv = player_ship.velocity
      shipspeed = Vector.length(shipv)
      if shipspeed < player_ship.speed - 5:
         return
      selfpos = self.ship.rect.center
      shippos = player_ship.rect.center
      shipwid = player_ship.rect.width
      for i, a in enumerate(self.shot_vectors):
         intersect = Vector.intersect(selfpos, a, shippos, shipv)
         if intersect is not None and Vector.dotp(Vector.v_sub(shippos, intersect), shipv) < 0:
            shiptime = (Vector.distance(shippos, intersect) + shipwid) / shipspeed
            selftime = Vector.distance(selfpos, intersect) / self.shot_speeds[i]
            diff = shiptime - selftime
            if diff < shipwid / (3.0*shipspeed) and diff > 0:
               return self.fire(player_ship)

